<template>
  <div class="flow-container w-full" @drop="onDrop">
    <VueFlow
      v-model:nodes="store.nodes"
      :apply-default="false"
      :edge-types="{ custom: CustomEdge }"
      :default-edge-options="{ type: 'custom' }"
      :fit-view-padding="[50, 50, 50, 50]"
      :fit-view="true"
      :connection-mode="ConnectionMode.Strict"
      @dragover="onDragOver"
      @dragleave="onDragLeave"
    >
      <template #node-custom="customNodeProps">
        <CustomNode v-bind="customNodeProps" />
      </template>
      <template #edge-custom="customEdgeProps">
        <CustomEdge v-bind="customEdgeProps" />
      </template>
      <template #connection-line="customEdgeProps">
        <CustomEdge v-bind="customEdgeProps" />
      </template>
    </VueFlow>
  </div>
</template>

<script setup lang="ts">
import { VueFlow, useVueFlow, ConnectionMode, EdgeChange } from '@vue-flow/core'
import CustomNode from './CustomNode.vue'
import CustomEdge from './CustomEdge.vue'
import { useFlowStore } from '@/store/modules/flow'
import useDragAndDrop from '@/store/modules/flow/useDnD'
import { FlowCustomNode } from '@/types'
import { ElMessage } from 'element-plus'
import { ProcessVO } from '@/api/system/process'

const store = useFlowStore()

const props = defineProps<{ info: ProcessVO }>()

const {
  onEdgeClick,
  onNodeClick,
  onConnect,
  addEdges,
  onNodesChange,
  onEdgesChange,
  applyNodeChanges,
  applyEdgeChanges,
  removeNodes,
  removeEdges,
  getEdges,
  toObject,
  fromObject
} = useVueFlow()

const { onDragOver, onDrop, onDragLeave } = useDragAndDrop()

onConnect(addEdges)

onNodesChange(async (changes) => {
  const nextChanges = []

  for (const change of changes) {
    nextChanges.push(change)
  }

  applyNodeChanges(nextChanges)
})

onEdgesChange(async (changes) => {
  const nextChanges = []
  let errorMsg = ''

  for (const change of changes) {
    switch (change.type) {
      case 'add':
        errorMsg = processAddEdge(change)
        break
      default:
        break
    }
    if (errorMsg.length === 0) nextChanges.push(change)
  }

  if (errorMsg.length) ElMessage.error(errorMsg)
  applyEdgeChanges(nextChanges)
})

function processAddEdge(change: EdgeChange): string {
  if (change.type !== 'add') return ''
  const { sourceNode, targetNode } = change.item
  if (handleLoopEdge(sourceNode, targetNode)) {
    return '不支持两个节点之间连线回环'
  }
  if (handleIsExistEdge(sourceNode, targetNode)) {
    return '每个节点只能存有一个子节点和父节点'
  }
  return ''
  function handleLoopEdge(sourceNode: FlowCustomNode, targetNode: FlowCustomNode) {
    return getEdges.value.some(
      (edge) => edge.targetNode.id === sourceNode.id && edge.sourceNode.id === targetNode.id
    )
  }

  function handleIsExistEdge(sourceNode: FlowCustomNode, targetNode: FlowCustomNode) {
    return getEdges.value.some(
      (edge) => edge.sourceNode.id === sourceNode.id || edge.targetNode.id === targetNode.id
    )
  }
}

onNodeClick(({ node }) => {
  store.onClickType = 'node'
  store.currentNode = node
})

onEdgeClick(({ edge }) => {
  store.onClickType = 'edge'
  store.currentEdge = edge
})

async function init() {
  store.removeEdges = removeEdges
  store.removeNodes = removeNodes
  store.flowToObj = toObject
}

watch(
  () => props.info.record,
  (record) => {
    // 请求API获取流程图配置
    if (record || record?.length > 0) {
      fromObject(JSON.parse(record))
    }
  }
)

onMounted(() => {
  init()
})
</script>

<style scoped></style>
